`timescale 1ns / 1ns

// Description: Async fifo
// Author: JerryTech
// License: GPLv3
// Refer to :   https://www.cnblogs.com/liushaowe1/p/16136749.html
//              https://blog.csdn.net/qq_38305370/article/details/115056361
//              https://www.runoob.com/w3cnote/verilog2-fifo.html

module async_fifo # (
    parameter DATA_WIDTH = 32,
    parameter ADDR_LEN   = 16,
    parameter PROGRAM_FULL_SET  = 10,
    parameter PROGRAM_FULL_CLR  = 4,
    parameter PROGRAM_EMPTY_SET = 5,
    parameter PROGRAM_EMPTY_CLR = 8
)
(
    input   wire        rd_clk,
    input   wire        rd_reset,

    input   wire        wr_clk,
    input   wire        wr_reset,

    input   wire        wr_en,
    input   wire [DATA_WIDTH - 1:0 ] wr_data,

    input   wire        rd_en,
    output  reg  [DATA_WIDTH - 1:0 ] rd_data,

    output  wire        wr_full,
    output  wire        rd_empty,
    output  reg         wr_full_program,
    output  reg         rd_empty_program   
);

localparam ADDR_WIDTH = $clog2(ADDR_LEN);

reg     [ADDR_WIDTH :0    ] wr_counter;
reg     [ADDR_WIDTH :0    ] rd_counter;

wire    [ADDR_WIDTH :0    ] wr_counter_green;
wire    [ADDR_WIDTH :0    ] rd_counter_green;

wire    [ADDR_WIDTH :0    ] wr_counter_bin;
wire    [ADDR_WIDTH :0    ] rd_counter_bin;

wire    [ADDR_WIDTH :0    ] wr_counter_program_cal;
wire    [ADDR_WIDTH :0    ] rd_counter_program_cal;

reg     [ADDR_WIDTH :0    ] wr_counter_green_sync0;
reg     [ADDR_WIDTH :0    ] rd_counter_green_sync0;
reg     [ADDR_WIDTH :0    ] wr_counter_green_sync1;
reg     [ADDR_WIDTH :0    ] rd_counter_green_sync1;

reg     [DATA_WIDTH - 1:0 ] ram_block [ADDR_LEN - 1:0 ];
//Write process

always @ (posedge wr_clk) begin
    if(wr_reset) begin
        wr_counter              <= 0;
    end
    else if(wr_counter_program_cal <= ADDR_LEN - 1 && wr_en) begin
        wr_counter              <= wr_counter + 1;
        ram_block[wr_counter[ADDR_WIDTH - 1:0]]   <= wr_data;
    end
end

//Read process

always @ (posedge rd_clk) begin
    if(rd_reset) begin
        rd_counter              <= 0;
    end
    else if(rd_counter_program_cal >= 1 && rd_en) begin
        rd_counter              <= rd_counter + 1;
        rd_data                 <= ram_block[rd_counter[ADDR_WIDTH - 1:0]];
    end
end

//Green code caculate

assign wr_counter_green = wr_counter ^ (wr_counter >>> 1);
assign rd_counter_green = rd_counter ^ (rd_counter >>> 1);

//Green code sync

always@(posedge wr_clk) begin
    if(wr_reset) begin
        rd_counter_green_sync0 <= 0;
        rd_counter_green_sync1 <= 0;
    end
    else begin
        rd_counter_green_sync0 <= rd_counter_green;
        rd_counter_green_sync1 <= rd_counter_green_sync0;
    end
end

always@(posedge rd_clk) begin
    if(rd_reset) begin
        wr_counter_green_sync0 <= 0;
        wr_counter_green_sync1 <= 0;
    end
    else begin
        wr_counter_green_sync0 <= wr_counter_green;
        wr_counter_green_sync1 <= wr_counter_green_sync0;
    end
end

//Green code to binary code

genvar i;
generate
    for(i=0;i<=ADDR_WIDTH;i = i + 1) begin : green_to_bin
        if(i == ADDR_WIDTH) begin
            assign wr_counter_bin[ADDR_WIDTH] = wr_counter_green_sync1[ADDR_WIDTH];
            assign rd_counter_bin[ADDR_WIDTH] = rd_counter_green_sync1[ADDR_WIDTH];
        end
        else begin
            assign wr_counter_bin[i] = wr_counter_bin[i + 1] ^ wr_counter_green_sync1[i];
            assign rd_counter_bin[i] = rd_counter_bin[i + 1] ^ rd_counter_green_sync1[i];
        end
    end
endgenerate

//Program full and empty signal generate

assign wr_counter_program_cal = rd_counter_bin[ADDR_WIDTH] == wr_counter[ADDR_WIDTH] ? 
                                wr_counter[ADDR_WIDTH - 1:0] - rd_counter_bin[ADDR_WIDTH - 1:0] :
                                ADDR_LEN - rd_counter_bin[ADDR_WIDTH - 1:0] + wr_counter[ADDR_WIDTH - 1:0];

assign rd_counter_program_cal = rd_counter[ADDR_WIDTH] == wr_counter_bin[ADDR_WIDTH] ?  
                                wr_counter_bin[ADDR_WIDTH - 1:0] - rd_counter[ADDR_WIDTH - 1:0] :
                                ADDR_LEN - rd_counter[ADDR_WIDTH - 1:0] + wr_counter_bin[ADDR_WIDTH - 1:0];

always@(posedge wr_clk) begin
    if(wr_reset) begin
        wr_full_program <= 0;
    end
    else if(wr_counter_program_cal >= PROGRAM_FULL_SET - 1) begin
        wr_full_program <= 1;
    end
    else if(wr_counter_program_cal <= PROGRAM_FULL_CLR - 1) begin
        wr_full_program <= 0;
    end
end

always@(posedge rd_clk) begin
    if(rd_reset) begin
        rd_empty_program <= 0;
    end
    else if(rd_counter_program_cal <= PROGRAM_EMPTY_SET - 1) begin
        rd_empty_program <= 1;
    end
    else if(rd_counter_program_cal >= PROGRAM_EMPTY_CLR - 1) begin
        rd_empty_program <= 0;
    end
end

assign wr_full = (wr_counter[ADDR_WIDTH] != rd_counter_bin[ADDR_WIDTH]) &&
                 (wr_counter[ADDR_WIDTH - 1:0] == rd_counter_bin[ADDR_WIDTH - 1:0]);   

assign rd_empty = (rd_counter[ADDR_WIDTH] == wr_counter_bin[ADDR_WIDTH]) &&
                  (rd_counter[ADDR_WIDTH - 1:0] == wr_counter_bin[ADDR_WIDTH - 1:0]);

endmodule

